import sim.util.*;
import sim.engine.*;

/*
 * This class represents any agent on the grid.  Each agent is either a Civilian or Cop. 
 */

public abstract class Agent implements Steppable{
	private static final long serialVersionUID = 1L;
	
	Int2D loc;
	
	double govtLegitimacy = 0;
	double grievance = 0;	
	double riskAversion = 0;
	double perceivedHardship = 0;
	double arrestProbability = 0;
	boolean active = false;
	int jailTerm = 0;
	
	public Agent(Int2D loc, double riskAversion, double perceivedHardship, double govtLegitimacy){
		this.loc = loc;
		this.riskAversion = riskAversion;
		this.perceivedHardship = perceivedHardship;
		this.govtLegitimacy = govtLegitimacy;
	}
	
	IntBag neighborsX = new IntBag();
	IntBag neighborsY = new IntBag();
	
	public void step(SimState state) {
		Rebellion reb = (Rebellion)state;
		
		//used for the vision of the agent:
		reb.grid.getNeighborsMaxDistance(loc.x, loc.y, reb.vision, false, neighborsX, neighborsY);
			
		
		/* Each agent must first move to an open space within their vision.
		If the agent is a civilian, he must become active or quiet using Epstein's calculations.
		If the agent is a cop, he must arrest an active civilian within his vision if there is one.*/
		
		if(this.jailTerm == 0){
			move(reb);
		}
		
		if (this instanceof Civilian && this.jailTerm == 0){
			setActive(reb);
		}
		
		if(this instanceof Cop){
			arrestSuspect(reb);
		}
		
	}
	
	private void arrestSuspect(Rebellion reb) {
		Bag suspects = new Bag(); //bag of suspects for arrest
		
		//Loop through all agents within vision
		for(int i = 0; i < neighborsX.size(); i++){
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);
			
			Bag agents = reb.grid.getObjectsAtLocation(x, y);
			
			//if there is an active agent in this location, add them to the bag of suspects.
			if(agents != null){
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
					
					if(agent.active){
						suspects.add(agent);
					}
				}
			}
		}
		
		/*If any active agents have been added to the bag of suspects,
		one is selected randomly and jailed for a random period of time (0 - maxJailTerm).  
		The cop then moves to the location of the suspect.*/
		if(suspects.size() > 0){
			Agent agent = (Agent) suspects.get(reb.random.nextInt(suspects.size()));
			
			agent.active = false;
			agent.jailTerm = reb.random.nextInt(reb.maxJailTerm);
			
			loc = new Int2D(agent.loc.x, agent.loc.y);				
			reb.grid.setObjectLocation(this, loc);
			
			reb.activeCount--;
			
			//if the random jail term is 0, only increase quiet count.
			if(agent.jailTerm > 0)
				reb.jailCount++;
			else
				reb.quietCount++;
		}
			
		
	}

	private void setActive(Rebellion reb) {		
		boolean alreadyActive = false;
		
		if(this.active)
			alreadyActive = true;
		
		int activeV = 0;
		int copsV = 0;
		
		//Loop through all the agents within vision and count the number of active civilians and cops
		for(int i = 0; i < neighborsX.size(); i++){
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);
			Bag agents = reb.grid.getObjectsAtLocation(x, y);
			
			if(agents != null){
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
					
					if(agent instanceof Civilian && agent.active){
						activeV++;
					}
					else if(agent instanceof Cop){
						copsV++;
					}
				}
			}		
		}
		
		/*Calculations for going active*/
		arrestProbability = 1 - Math.exp(-2.3*((copsV/(activeV+1))));
		grievance = perceivedHardship * (1 - govtLegitimacy);
		this.active = (grievance - (riskAversion * arrestProbability)) > reb.threshold;
		
		if(!alreadyActive && this.active){
			reb.activeCount++;
			reb.quietCount--;
		}
		else if(alreadyActive && !this.active){
			reb.activeCount--;
			reb.quietCount++;
		}
	}
	
	private void move(Rebellion reb){	
		Bag freeSpaces = new Bag(); // bag of free spaces
		
		//Loop through all agents within vision
		for(int i = 0; i < neighborsX.size(); i++){			
			int x = neighborsX.get(i);
			int y = neighborsY.get(i);	
			
			Bag agents = reb.grid.getObjectsAtLocation(x, y);
			
			//if the space is empty, add it to the bag of free spaces
			if(agents == null){
				freeSpaces.add(new Int2D(x, y));
			}
			//if the space is occupied by agents that are in jail, add it to the bag of free spaces
			else if(agents != null){
				boolean freeSpace = false;
				
				Agent agent;
				for(int j = 0; j < agents.size(); j++){
					agent = (Agent)agents.get(j);
				
					if(agent.jailTerm > 0){
						freeSpace = true;		
					}
					else{
						freeSpace = false;
						break;
					}
				}
				
				if(freeSpace == true){
					freeSpaces.add(new Int2D(x, y));
				}
			}
		}
		
		//select random location from free spaces bag and move there
		if(freeSpaces.size() > 0){
			loc = (Int2D) freeSpaces.get(reb.random.nextInt(freeSpaces.size()));
			reb.grid.setObjectLocation(this, loc);
		}
	}
	
	public double getGovtLegitimacy(){return govtLegitimacy;}

}
